/* The Simulate Jobs class takes a cursor with the jobs we want simulated, and 
 * simulates the jobs based on the number of machines we specify
 */
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;
import java.text.DecimalFormat;
import java.util.*;
import java.util.List;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
//import java.nio.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;



public class SimulateJobs extends JPanel{

	DBCursor jobCursor;
	DBCollection job_events;
	BasicDBObject keys = new BasicDBObject();

	boolean DEBUG = true;
	
	// Machine and Job Resources
	int numMachines;
	final static double MACHINE_CAP = 1.0; 
	//final static double FACTOR = 1e-2;
	double availableCPU[],  availableRAM[];
	Random PRNG = new Random();
	//LinkedList<Job>[] machines;     // The list of jobs for a specific Machine MACHINEREPLACE
	List<machineList> machines2;
	PriorityQueue<Event> eventList; // The list of upcoming events 

	double clock;							// The simulator's clock
	
	// Machine Killing Resources
	int rescheduleCounter;
	String killedMachineMessage = "";
	boolean jobsKilled;
	List<Integer> rescheduleMachinesList;
	List<Integer> downedMachinesList;

	// Statistics.
	int numArrivals = 0, numDepartures = 0, numScheduled=0, numWaiting = 0, 
	numJobTime = 0, numKilled= 0, numFailed = 0, numLost = 0;
	//double totalWaitTime, avgWaitTime;      // For time spent in queue.
	//double totalSystemTime, avgSystemTime;  // For time spent in system.
	//double totalJobTime;					// For the job completion time

	// Graph Resources
	final static int MAX_POINTS = 50;
	int pointsCount;
	double xAxisTime[], yAxisJobs[], yAxisResourcesRAM[], yAxisResourcesCPU[];
	double ramUsage = 0.0, cpuUsage = 0.0;
	boolean hasLooped = false;

	// Animation and drawing.
	Thread simulationThread;  
	boolean isPaused = false;
	int sleepTime = 50;
	DecimalFormat df = new DecimalFormat ("##.####");
	
	// Output Error Log Resources
	File rescheduleLogPath = new File(System.getProperty("user.home") + "/SimulatorLogs/reschedule_log.txt");
	
	/*
	 * cursor is passed with the jobs we want to simulate, the job collection, 
	 * and number of machines
	 */
	public SimulateJobs (DBCursor cursor,DBCollection jobEvents, int machineNum)
	{
		jobCursor = cursor;
		keys.put("job_id", 1);
		keys.put("event_type", 1);
		keys.put("time", 1);
		job_events = jobEvents;
		numMachines = machineNum;
	}

	void reset ()
	{
		eventList = new PriorityQueue<Event>();

		//give each machine a list of jobs, initialize machine availability
		//machines = new LinkedList[numMachines]; // The list of jobs for a specific Machine MACHINEREPLACE
		machines2 = new ArrayList<machineList>(numMachines);
		availableCPU = new double[numMachines];
		availableRAM = new double[numMachines];

		// Graph Resources
		xAxisTime = new double[MAX_POINTS];
		yAxisJobs = new double[MAX_POINTS];
		yAxisResourcesCPU = new double[MAX_POINTS];
		yAxisResourcesRAM = new double[MAX_POINTS];
		pointsCount = -1;
		clock = 0;
		
		for (int i = 0; i < numMachines; i++){
			availableCPU[i] = MACHINE_CAP;
			availableRAM[i] = MACHINE_CAP;
			//machines[i] = new LinkedList<Job>(); MACHINEREPLACE
			machines2.add(new machineList());
		}
		
		// Initialize machine killing resources
		rescheduleCounter = -1;
		rescheduleMachinesList = new ArrayList<Integer>();
		downedMachinesList = new ArrayList<Integer>();
		machineKilled = false;
		jobsKilled = false; 
		
		// Initialize stats variables.
		numArrivals = numDepartures = numScheduled = numKilled = numLost = numFailed = 0;

		// Need to have at least one event in event list.
		//scheduleJobArrival();
	}

	//Runs one step of the simulation
	void nextStep () throws IOException
	{
		System.out.println("\n***Next Step***");

		System.out.println(" reschedule counter at " + rescheduleCounter);
		System.out.println(" rescheduleMachinesList contains: " + rescheduleMachinesList.toString());
		/*
		if(rescheduleCounter == 0)
		{
			System.out.println("  Rescheduling started!");
			handleRescheduling();

		}
		else if(rescheduleCounter > 0)
			rescheduleCounter--;
		*/
		while (eventList.isEmpty()) {
			//System.out.println ("EventList empty. Simulation Complete.");
			scheduleJobArrival();//return;
		}

		Event e = eventList.poll ();
		clock = e.eventTime;
		
		if (e.type == Event.ARRIVAL) {
			if(DEBUG)
				System.out.println("Arrival: " + clock);
			//System.out.println("numArrival: " + numArrivals + " numScheduled: " + numScheduled + " numDeparture " + numDepartures + " numLost: " + numLost + " numFailed: " + numFailed + " numKilled: " + numKilled);
			handleJobArrival (e);
		}
		else if (e.type == Event.SCHEDULED) {
			if(DEBUG)
				System.out.println("Departure: " + clock);
			handleJobDeparture (e);
		}
		else if(e.type == Event.FAILED || e.type == 
			Event.KILLED || e.type == Event.LOST) {
			if(DEBUG)
				System.out.println("LOST-KILLED-FAILED: " + clock);
			handleLOKIFA(e);
		}		

		// Do stats after event is processed.
		//stats ();

		pointsCount++;
		// Graph Resources Bookkeeping
		if(pointsCount >= MAX_POINTS)
		{
			pointsCount = 0;
			hasLooped = true;
		}
		xAxisTime[pointsCount] = clock;
		yAxisJobs[pointsCount] = numScheduled;
		if(cpuUsage >= 1.0)
			cpuUsage = 1;
		if(ramUsage >= 1.0)
			ramUsage = 1;
		yAxisResourcesCPU[pointsCount] = cpuUsage;
		yAxisResourcesRAM[pointsCount] = ramUsage;

		//if (numDepartures % 1000 == 0) {
		//	System.out.println ("After " + numDepartures );//+ 
					//" departures: avgWait=" + avgWaitTime + "  " +
					//"avgSystemTime=" + avgSystemTime);
		//}
	}

	// If there is another job, add its arrival to the event queue
	// The job's CPU and RAM requirements have been randomly generated.
	// The request is in terms of percentage and can be up to 50% of 
	// a machine's CPU/RAM.
	void scheduleJobArrival()
	{
		if(jobCursor.hasNext())
		{
			numArrivals++;
			jobCursor.next();
			String job_id = jobCursor.curr().get("job_id").toString();

			double time = Double.parseDouble(jobCursor.curr().get("time").toString());
			System.out.println("time: " +time);
			System.out.println("  CURSOR TYPE: " + (jobCursor.curr().get("event_type")) + " CURSOR ID:" + job_id);
			eventList.add (new Event (time, Event.ARRIVAL, 0, new Job(time,job_id, 
					PRNG.nextDouble() * 0.5, PRNG.nextDouble() * 0.5 )));
		}
	}

	// Once a job has arrived, put it onto a machine's queue
	void handleJobArrival (Event e)
	{
		int pos = chooseMachineSmrt(e.job.cpuUsage, e.job.ramUsage);
		//if(pos > machines.length){	// if all machines are running at capacity MACHINEREPLACE
		if(pos > machines2.size()){
			pos = chooseMachine();	// choose random machine
			numWaiting++;
			if(DEBUG)
				System.out.println(" Waiting: " + pos + " " + e.job.cpuUsage + " " + e.job.ramUsage);
		}
		else{	// otherwise job is 'running'
			System.out.println("Machine: " + pos + " now has " + (machines2.get(pos).jobList.size() + 1) + " jobs running or queued.");
			scheduleJobDeparture(e.job, pos);	// schedule departure
		}
		machines2.get(pos).jobList.add(e.job);
		/*if (machines[pos].size() == 1) {
			// This is the only customer => schedule a departure.
			scheduleJobDeparture(e.job, pos);
		}*/
		scheduleJobArrival(); // add another job to the event queue
	}

	// This means a job has been 'started'. 
	// Add the time it took to be completed to the current clock time to get end time
	void scheduleJobDeparture (Job job, int pos)
	{
		double endTime, totalTime;
		int event, event_type = 0;
		BasicDBObject jobQuery = new BasicDBObject();
		jobQuery.put("job_id", job.jobID);
		jobQuery.put("event_type", "4");

		DBCursor cursor = job_events.find(jobQuery, keys);
		if(cursor.hasNext()) // Has end time
		{
			cursor.next();
			totalTime = (Double.parseDouble(cursor.curr().get("time").toString())) - job.entryTime;	
			event_type = Event.SCHEDULED;
		}
		else
		{
			BasicDBObject endQuery = (BasicDBObject) QueryBuilder.start().or(new BasicDBObject("event_type", "3"), new BasicDBObject("event_type", "5"), new BasicDBObject("event_type", "6")).get();
			endQuery.put("job_id", job.jobID);
			cursor = job_events.find(endQuery,keys);
			if(cursor.hasNext()){
				cursor.next();
				event = Integer.parseInt(cursor.curr().get("event_type").toString());
				totalTime = (Double.parseDouble(cursor.curr().get("time").toString())) - job.entryTime;
				switch (event){
					case 3:		event_type = Event.FAILED; 
								break;
					case 5: 	event_type = Event.KILLED; 
								break;
					case 6:	 	event_type = Event.LOST; 
								break;
					default: 	if(DEBUG)
									System.out.println("3,5,6 not returned. returned: " + event);
								return;
				}
			}
			else{
				if(DEBUG)
					System.out.println("  Job endTime either type 2,7,8: job_id = " + job.jobID);
				//System.out.println("  " + machines2.get(pos).jobList.size());
				//System.out.println("Subtracted from numarrivals");
				numArrivals--;
				return;
			}
		}
		
		job.isScheduled = true;
		availableCPU[pos] -= job.cpuUsage;
		availableRAM[pos] -= job.ramUsage;
		cpuUsage += job.cpuUsage/numMachines;
		ramUsage += job.ramUsage/numMachines;
		numScheduled++;
		//totalJobTime += totalTime;
		numJobTime++;
		if(DEBUG)
			System.out.println(" DEPARTURE SCHEDULED on: " + pos + " " + job.cpuUsage + " " + job.ramUsage);
		endTime = clock + totalTime;
		job.endTime = endTime;
		eventList.add (new Event (endTime, event_type, pos, job));
		//System.out.println("has next");
	}

	// Removes a job from a machine. 
	// Then if there is another job scheduled on the machine, schedule its departure
	void handleJobDeparture (Event e)
	{
		numDepartures ++;
		numScheduled--;
		int k = e.machine;
		Job j = e.job;
		availableCPU[k] += j.cpuUsage;
		availableRAM[k] += j.ramUsage;
		cpuUsage -= j.cpuUsage/numMachines;
		ramUsage -= j.ramUsage/numMachines;
		//machines[k].remove(e.job); MACHINEREPLACE
		machines2.get(k).jobList.remove(e.job);
		//totalSystemTime += clock - j.entryTime;
		//if (machines[k].size() > 0) { MACHINEREPLACE
		if(machines2.get(k).jobList.size() > 0) {
			scheduleJobsForMachine(k);
		}
	}

	void scheduleJobsForMachine(int k){
		for(int i = 0; i < machines2.get(k).jobList.size(); i++) 
		{			
			Job nextJob = machines2.get(k).jobList.get(i);
			if(nextJob.isScheduled == false){
				if(availableCPU[k] > nextJob.cpuUsage && availableRAM[k] > nextJob.ramUsage)
				{
					// Time jobs spent in queue
					scheduleJobDeparture (nextJob, k);
					numWaiting--;
				}
				else	//return so that a larger job will not starve
					return;
			}

		} 
	}

	void handleLOKIFA(Event e)
	{
		numScheduled--;
		if(e.type == Event.FAILED)
			numFailed++;
		else if(e.type == Event.KILLED)
			numKilled++;
		else if(e.type == Event.LOST)
			numLost++;
		int k = e.machine;
		Job j = e.job;
		availableCPU[k] += j.cpuUsage;
		availableRAM[k] += j.ramUsage;
		cpuUsage -= j.cpuUsage/numMachines;
		ramUsage -= j.ramUsage/numMachines;

		machines2.get(k).jobList.remove(e.job);
	}

	int chooseMachine()
	{
		// Shortest queue.
		int shortestSize = Integer.MAX_VALUE, shortest = 0;
		for(int i = 0; i < numMachines; i++)
		{
			if(machines2.get(i).jobList.size() < shortestSize && availableCPU[i] != 0 && availableRAM[i] != 0) 
			{
				shortestSize = machines2.get(i).jobList.size();
				shortest = i;
			}
		}
		return shortest;
	}

	// Similar to chooseMachine(), but checks through all available machines
	// to find the "best" one, i.e. optimize for minimum resulting available
	// resource out of all machines
	int chooseMachineSmrt(double cpuUsage, double ramUsage)
	{
		int shortestSize = Integer.MAX_VALUE;
		double cpuAvailable = MACHINE_CAP, ramAvailable = MACHINE_CAP;
		double minCPU = Double.MAX_VALUE, minRAM = Double.MAX_VALUE;

		for(int i = 0; i < numMachines; i++)
		{
			if(availableCPU[i] <= 0 || availableRAM[i] <= 0)
			{
				if(DEBUG)
					System.out.println("  Continue");
				continue;
			}
			else
			{
				// If machine is not empty, calculate minimums
				if(!machines2.get(i).jobList.isEmpty())
				{
					cpuAvailable = availableCPU[i];
					ramAvailable = availableRAM[i];

					// Checks whether the difference yields a minimum in BOTH
					// RAM and CPU usage.
					if((cpuAvailable - cpuUsage) <= minCPU && (ramAvailable - ramUsage) <= minRAM)
					{
						shortestSize = i;
						minCPU = cpuAvailable - cpuUsage;
						minRAM = ramAvailable - ramUsage;
					}
					cpuAvailable = ramAvailable = MACHINE_CAP;
				}
				else // There is nothing currently scheduled on the machine
				{
					System.out.println(" Have request for " + cpuUsage + " CPU and " + ramUsage + " RAM for machine " + i);
					return i;
				}
			}
		}
		System.out.println(" Have request for " + cpuUsage + " and " + ramUsage + " for machineSS " + shortestSize);
		return shortestSize;		
	}

	void stats ()
	{
		if (numDepartures == 0) {
			return;
		}
		//avgWaitTime = totalWaitTime / numDepartures;
		//avgSystemTime = totalSystemTime / numDepartures;
	}

	// Used to calculate how much of all machines are utilized
	// Not done yet.
	void utilizationState()
	{
		double sumCPU = 0, sumRAM = 0;

		for(int i = 0; i < numMachines; i++)
		{
			sumCPU += availableCPU[i];
			sumRAM += availableRAM[i];
		}

	}

	// Assumes we already know which machine to send the job to
	// Otherwise we can probably try to run chooseMachineSmrt to find
	// a good place to send the job to. If the Src == Dst, then there's
	// no place to migrate to	
	// We'll give priority to a user specificed machineDst. But if there aren't
	// sufficient resources, we'll choose another machine if possible
	int jobMigrate(int machineSrc, int machineDst, String jobName)
	{
		int jobIndex, optMachineDst;
		Job jobSrcTmp;

		jobSrcTmp = new Job();		
		optMachineDst = machineDst;
		jobIndex = machines2.get(machineSrc).getJobIndex(jobName); // index of job
		jobSrcTmp = machines2.get(machineSrc).jobList.get(jobIndex); // temp job

		if(availableCPU[machineDst] < jobSrcTmp.cpuUsage ||
				availableRAM[machineDst] < jobSrcTmp.ramUsage)
			if(chooseMachineSmrt(jobSrcTmp.cpuUsage, jobSrcTmp.ramUsage) == machineDst)
				return -1; // Error if insufficient resources

		machines2.get(machineDst).addToJobList(jobSrcTmp.entryTime,  
				jobSrcTmp.jobID, jobSrcTmp.cpuUsage, jobSrcTmp.ramUsage); // Add the job to dst
		if(availableRAM[machineDst] < jobSrcTmp.ramUsage)
		{
			optMachineDst = chooseMachineSmrt(jobSrcTmp.cpuUsage, jobSrcTmp.ramUsage);
			if(optMachineDst == machineDst || optMachineDst == Integer.MAX_VALUE)
				return -1; // Error if insufficient resources		
		}

		machines2.get(optMachineDst).addToJobList(jobSrcTmp.entryTime, jobSrcTmp.jobID, 
				jobSrcTmp.cpuUsage, jobSrcTmp.ramUsage); // Add the job to dst
		machines2.get(machineSrc).jobList.remove(jobIndex); // Remove job from src

		return 0;
	}
	
	/* "Kills" a randomly chosen machine. The way that the machine is killed is by 
	 *	setting its available resources to be 0. Job history are assumed to have been
	 * preserved through logs. 
	 */
	void killMachineRandom() throws IOException
	{
		int machinePos;
		
		machinePos = PRNG.nextInt(numMachines);
		
		//if(downedMachinesList.isEmpty())
		if(rescheduleCounter < 0)
			rescheduleCounter = 4;
		
		if(!downedMachinesList.contains(machinePos))
		{
			downedMachinesList.add(machinePos);
			if(!machines2.get(machinePos).jobList.isEmpty())
			{
				rescheduleMachinesList.add(machinePos);
				jobsKilled = true;
			}
			killedMachineMessage = "Machine: " + machinePos + " has failed. There are " +
				machines2.get(machinePos).jobList.size() + " jobs that will be rescheduled.";
			System.out.println(killedMachineMessage);

			//outputMsgToLog(rescheduleLogPath, killedMachineMessage);
			machineKilled = true;
		}
		else
			System.out.println("No effect. Machine: " + machinePos + " is already down.\n");
		
		cpuUsage += (1 - availableCPU[machinePos])/numMachines;
		ramUsage += (1 -availableRAM[machinePos])/numMachines;
		availableCPU[machinePos] = 0;
		availableRAM[machinePos] = 0;
		
		if(jobsKilled)
			handleRescheduling();
	}
	
	/*
	 * Reinitializes all failed machines
	 */
	void restartFailedMachines()
	{
		int machinePos = -1;
		
		if(downedMachinesList.isEmpty())
			return;
		
		cpuUsage -= downedMachinesList.size()/numMachines;
		ramUsage -= downedMachinesList.size()/numMachines;
		
		while(!downedMachinesList.isEmpty())
		{
			machinePos = downedMachinesList.remove(0);
			availableCPU[machinePos] = 1;
			availableRAM[machinePos] = 1;
			System.out.println("Machine: " + machinePos + " has restarted!");
		}
		machineKilled = false;
	}
	
	/*
	 * Handles rescheduling of jobs that were killed due to a failed machine
	 * Success or failure to reschedule of the jobs are logged according to 
	 * the rescheduleLogPath directory 
	 */
	void handleRescheduling() throws IOException
	{
		int reschedulePos, randomTime = 1000000;
		double startTimeInit, endTimeInit;
		boolean jobIDMismatch = false, endTimeMismatch = false, typeMismatch = false; 
		String job_id, rescheduleLogString;
		
		LinkedList<Job> rescheduleJobList;
		Iterator<Event> eventIt;
		Event e = null; 
		
		if(rescheduleMachinesList.isEmpty())
		{
			//outputMsgToLog(rescheduleLogPath, "Reschedule function called, but no jobs to reschedule\n");
			System.out.println("Reschedule function called, but no jobs to reschedule");
			return;
		}
		
		reschedulePos = rescheduleMachinesList.remove(0);
		System.out.println("  Rescheduling jobs from machine " + reschedulePos);
		rescheduleJobList = machines2.get(reschedulePos).jobList;

		// Look through the list of jobs waiting to be rescheduled
		for(int i = 0; i < rescheduleJobList.size(); i++)
		{
			//System.out.println(" RescheduleJobList is: " + rescheduleJobList.toString());
			if(rescheduleJobList.get(i).isScheduled == false)
			{
				System.out.println("Job " + rescheduleJobList.get(i).jobID + " was not scheduled. Scheduling now");
				eventList.add(new Event(clock + randomTime, Event.ARRIVAL, 0, 
						new Job((clock + randomTime), rescheduleJobList.get(i).jobID, 
						PRNG.nextDouble() * 0.5, PRNG.nextDouble() * 0.5 )));
				rescheduleJobList.remove(i);
				numArrivals++;
				continue;
			}
				
			e = null;
			eventIt = eventList.iterator();
			rescheduleLogString = "empty string";
			
			job_id = rescheduleJobList.get(i).jobID;
			startTimeInit = rescheduleJobList.get(i).entryTime;
			endTimeInit = rescheduleJobList.get(i).endTime;

			// Parse through the entire eventList for the killed jobs
			// Look for the job's previous endtime and update it
			while(eventIt.hasNext()) {
				System.out.println("  In reschedule while");
				e = eventIt.next();
				// Check for endtime mismatch

				if(e.eventTime == endTimeInit) {
					System.out.println("  Matched end time");
					// Check for jobID mismatch
					if(e.job.jobID.equals(job_id)) {
						System.out.println("  Matched job id");
						// Check for event type mismatch
						if(e.type == 2 || e.type == 3 || e.type == 4 || e.type == 5) {
							randomTime = PRNG.nextInt(10000000);
							System.out.println("  Matched type");
							e.type = Event.ARRIVAL;
							e.eventTime = clock + (endTimeInit - startTimeInit) + randomTime;
							e.job.isScheduled = true;
							if(DEBUG)
								rescheduleLogString = "Job rescheduled" +
									'\n' + "  job_ID: " + job_id +
									'\n' + "  New starttime: " + clock + 
									'\n' + "  endtime: " + e.eventTime +
									'\n' + "  type: " + e.type;
							typeMismatch = jobIDMismatch = endTimeMismatch = false;
							break;
						} else
							if(DEBUG)
								typeMismatch = true;
					} else
						if(DEBUG)
							jobIDMismatch = true;
				} else
					if(DEBUG)
						endTimeMismatch = true;	
			}
			if(DEBUG){ 
				if(typeMismatch && e != null) 
				{
					rescheduleLogString = "  Reschedule error: no eventType matches" +
							'\n' + "  Failed job_ID: " + job_id +
							'\n' + "  endtime: " + e.eventTime +
							'\n' + "  type: " + e.type;
					//rescheduleJobList.remove(i);
					//System.out.println("Removed job we don't care about. Type is: " + e.type);
				}
				else if(jobIDMismatch && e != null)		
					rescheduleLogString = "  Reschedule error: no jobID matches" +
							'\n' + "  Failed job_ID: " + job_id +
							'\n' + "  endtime: " + e.eventTime +
							'\n' + "  type: " + e.type;
				else if(endTimeMismatch && e != null)
					rescheduleLogString = "  Reschedule error: no endTime matches" +
							'\n' + "  Failed job_ID: " + job_id +
							'\n' + "  endtime: " + e.eventTime +
							'\n' + "  type: " + e.type;
				else
				{
					eventList.add (new Event (clock * randomTime, Event.ARRIVAL, 0, 
							new Job((clock * randomTime),job_id, 
							PRNG.nextDouble() * 0.5, PRNG.nextDouble() * 0.5 )));
					numArrivals++;
					rescheduleJobList.remove(i);
					//outputMsgToLog(rescheduleLogPath, rescheduleLogString);				
				}
				System.out.println(rescheduleLogString);
				i--;
			}
		}
		//if(!rescheduleMachinesList.isEmpty())
		//	rescheduleCounter = 4;
	}
	
	/* Used to write messages to a file
	 * Credit for the implementation goes to Anthony Accioly
	 * http://goo.gl/OM2hc
	 */
	/*
	void outputMsgToLog(File filePath, String message) throws IOException
	{
		byte[] outputByte; // for file IO
		long outputSize;
		FileChannel outputChannel;
		MappedByteBuffer outputByteBuffer; 
		
		outputByte = message.getBytes(Charset.forName("ISO-8859-1"));
		outputSize = outputByte.length;
		RandomAccessFile outputFile = new RandomAccessFile(filePath, "rw");
		outputFile.seek(outputFile.length());
		outputChannel = outputFile.getChannel();
		outputByteBuffer = outputChannel.map(FileChannel.MapMode.READ_WRITE, 
				outputChannel.position(), outputSize); 
		outputChannel.close();
		outputByteBuffer.put(outputByte);
	}
	*/

	/************************************************************
	 * 						UI Methods							*
	 ************************************************************/

	// Called when Go button is clicked
	// Starts a new thread that runs simulation
	void go()
	{
		if(isPaused) {
			isPaused = false;
			return;
		}
		//user pressed go again when a simulation is in progress
		if(simulationThread != null) {	
			simulationThread.interrupt();
			simulationThread = null;
		}

		//start a new thread that runs simulate() method
		simulationThread = new Thread() {
			public void run() 
			{	
				try {
					simulate();
				} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				}	
			}
		};
		simulationThread.start();
	}

	// Called when Pause button is clicked
	void pause() 
	{
		isPaused = true;
	}

	// Method called by the thread from go()
	void simulate() throws IOException
	{
		while(true) {

			if(! isPaused) {
				nextStep();
			}

			this.repaint();

			try {
				Thread.sleep(sleepTime);
			}
			catch(InterruptedException e){
				break;
			}


		} 

		this.repaint();
	}


	/************************************************************
	 * 						GUI Stuff							*
	 ************************************************************/
	int jobGraphx1=450, jobGraphx2=50, jobGraphy1=500, jobGraphy2=250;
	int resourceGraphx1=975, resourceGraphx2=575, resourceGraphy1=500, resourceGraphy2=250;
	boolean machineKilled = false;


	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);

		Dimension D = this.getSize();
		g.setColor(Color.white);
		g.fillRect(0,0, D.width,D.height);

		// Current averages.
		String clockString = "Clock: " + clock + " ms";
		String departString = "Number Departures:" + numDepartures;
		String arriveString = "Number of Arrivals: " + numArrivals;
		String scheduleString = "Number Running: " + numScheduled;
		//String waitString = "Average Wait: " + df.format(avgWaitTime); 
		//String totalTimeString = "Average Time in System: " + df.format(avgSystemTime);
		String eventListString = "EventList Size: " + eventList.size();
		String failedString = "Number Failed: " + numFailed;
		String lostString = "Number Lost: " + numLost;
		String killedString = "Number Killed: " + numKilled;
		String waitString = "Number Waiting: " + numWaiting;
		String machinesDownString = "Number of Failed Machines: " + downedMachinesList.size();

		g.setColor(Color.darkGray);
		g.drawString(clockString, 10, 30);
		g.drawString(arriveString, 10, 50);
		g.drawString(scheduleString, 10, 70);
		g.drawString(departString, 10, 90);
		g.drawString(eventListString, 10, 110);
		g.drawString(waitString, 10, 130);
		g.drawString(failedString, 10, 150);
		g.drawString(killedString, 10, 170);
		g.drawString(lostString, 10, 190);
		
		DecimalFormat usageFormat = new DecimalFormat("#.####"); 
		String cputest = "cpuUsage: " + usageFormat.format(cpuUsage);
		String ramtest = "ramUsage: " + usageFormat.format(ramUsage);
		g.setColor(Color.RED);
		g.drawString(cputest, 10, 210);
		g.setColor(Color.BLUE);
		g.drawString(ramtest, 10, 230);
		g.setColor(Color.BLACK);
		
		
		if(machineKilled)
			g.drawString(killedMachineMessage, 400, 70);
		g.drawString(machinesDownString, 400, 90);
		
		// Graph 1 Axes Labels
	    g.drawString("JOBS", jobGraphx2 - 35, jobGraphy2 + 130);
	    g.drawString("TIME STEPS", jobGraphx2 + 150, jobGraphy1 + 20);
	    
	    // Graph 2 Axes Labels
	    g.drawString("RESOURCES", resourceGraphx2 - 80, jobGraphy2 + 130);
	    g.drawString("TIME STEPS", resourceGraphx2 + 175, jobGraphy1 + 20);
	    
		drawAxes(g, jobGraphx1, jobGraphx2, jobGraphy1, jobGraphy2);
		drawAxes(g, resourceGraphx1, resourceGraphx2, resourceGraphy1, resourceGraphy2);
		drawGraph(g,jobGraphx1, jobGraphx2, jobGraphy1, jobGraphy2, yAxisJobs);
		drawGraph(g, resourceGraphx1, resourceGraphx2, resourceGraphy1, resourceGraphy2, yAxisResourcesCPU, yAxisResourcesRAM);
	}

	public void drawAxes(Graphics g,int x1, int x2, int y1, int y2)
	{
		g.drawLine(x2,y1,x1,y1); // x axis
		g.drawLine(x2,y2,x2,y1); // y axis
	}

	public void drawGraph(Graphics g,int x1, int x2, int y1, int y2, double[] yArray)
	{
		int pointDiameter = 2;
		int prevX = 0, prevY = 0;
		int XAxisDist = x1-x2;
		int YAxisDist = y1-y2;

		double YMax = numMachines*7, YMin = 0;

		double totalYDifference = YMax-YMin;
		if(hasLooped)
		{
			int interval = XAxisDist/(MAX_POINTS-1);

			// Get PrevPoint
			double yRel = (yArray[(pointsCount+1)%MAX_POINTS]-YMin)/totalYDifference;
			prevX = x2;
			prevY = y1-(int)Math.round(yRel*YAxisDist);
			g.drawOval(prevX, prevY, pointDiameter, pointDiameter);
			int count = 1;
			for(int i = pointsCount + 2; i < MAX_POINTS; i++)
			{
				double y = yArray[i];
				double yRelative = (y-YMin)/totalYDifference;
				int xPoint = (count)*interval + x2;
				int yPoint = y1-(int)Math.round(yRelative*YAxisDist);
				g.drawOval(xPoint, yPoint, pointDiameter, pointDiameter);
				g.drawLine(prevX, prevY, xPoint, yPoint);
				prevX = xPoint;
				prevY = yPoint;
				count++;
			}
			int start = 0;
			if(pointsCount == 4)
				start = 1;
			for(int i = start; i <= pointsCount; i++)
			{
				double y = yArray[i];
				double yRelative = (y-YMin)/totalYDifference;
				int xPoint =(count)*interval + x2;
				int yPoint = y1-(int)Math.round(yRelative*YAxisDist);
				g.drawOval(xPoint, yPoint, pointDiameter, pointDiameter);
				g.drawLine(prevX, prevY, xPoint, yPoint);
				prevX = xPoint;
				prevY = yPoint;
				count++;
			}

		}
		else {
			if(pointsCount == -1)
				return;
			int interval = 0;
			// Get First Point
			if(pointsCount > 0){
				double yRel = (yArray[0]-YMin)/totalYDifference;
				prevX = x2;
				prevY = y1 -(int)Math.round(yRel*YAxisDist);
				g.drawOval(prevX, prevY, pointDiameter, pointDiameter);
				interval = XAxisDist/pointsCount;
			}
			else{
				g.drawOval(x2, y1, pointDiameter, pointDiameter);
				prevX = x2;
				prevY = y1;
			}
			for(int i = 1; i <= pointsCount; i++)
			{
				double y = yArray[i];
				double yRelative = (y-YMin)/totalYDifference;
				int xPoint = x2 + i*interval;
				int yPoint = y1 - (int)Math.round(yRelative*YAxisDist);
				g.drawOval(xPoint, yPoint, pointDiameter, pointDiameter);
				g.drawLine(prevX, prevY, xPoint, yPoint);
				prevX = xPoint;
				prevY = yPoint;
			}
		}
	}

	public void drawGraph(Graphics g,int x1, int x2, int y1, int y2, double[] yArray1, double[] yArray2)
	{
		int pointDiameter = 2;
		int prevX = 0, prevY1 = 0, prevY2 = 0;
		int XAxisDist = x1-x2;
		int YAxisDist = y1-y2;

		if(hasLooped)
		{
			int interval = XAxisDist/(MAX_POINTS-1);

			// Get Prev Point for y1
			double yRel1 = (yArray1[(pointsCount+1)%MAX_POINTS]);
			prevX = x2;
			prevY1 = y1-(int)Math.round(yRel1*YAxisDist);
			g.setColor(Color.RED);
			g.drawOval(prevX, prevY1, pointDiameter, pointDiameter);
			// Get Prev Point for y2
			double yRel2 = (yArray2[(pointsCount+1)%MAX_POINTS]);
			prevY2 = y1-(int)Math.round(yRel2*YAxisDist);
			g.setColor(Color.BLUE);
			g.drawOval(prevX, prevY2, pointDiameter, pointDiameter);
			int count = 1;
			for(int i = pointsCount + 2; i < MAX_POINTS; i++)
			{
				double yRelative = yArray1[i];
				int xPoint = (count)*interval + x2;
				int yPoint = y1-(int)Math.round(yRelative*YAxisDist);
				g.setColor(Color.RED);
				g.drawOval(xPoint, yPoint, pointDiameter, pointDiameter);
				g.drawLine(prevX, prevY1, xPoint, yPoint);
				prevY1 = yPoint;

				yRelative = yArray2[i];
				int yPoint2 = y1-(int)Math.round(yRelative*YAxisDist);
				g.setColor(Color.BLUE);
				g.drawOval(xPoint, yPoint2, pointDiameter, pointDiameter);
				g.drawLine(prevX, prevY2, xPoint, yPoint2);
				prevX = xPoint;
				prevY2 = yPoint2;

				count++;
			}
			int start = 0;
			if(pointsCount == 4)
				start = 1;
			for(int i = start; i <= pointsCount; i++)
			{
				double yRelative = yArray1[i];
				int xPoint =(count)*interval + x2;
				int yPoint1 = y1-(int)Math.round(yRelative*YAxisDist);
				g.setColor(Color.RED);
				g.drawOval(xPoint, yPoint1, pointDiameter, pointDiameter);
				g.drawLine(prevX, prevY1, xPoint, yPoint1);
				prevY1 = yPoint1;

				yRelative = yArray2[i];
				int yPoint2 = y1-(int)Math.round(yRelative*YAxisDist);
				g.setColor(Color.BLUE);
				g.drawOval(xPoint, yPoint2, pointDiameter, pointDiameter);
				g.drawLine(prevX, prevY2, xPoint, yPoint2);
				prevX = xPoint;
				prevY2 = yPoint2;
				count++;
			}

		}
		else {
			if(pointsCount == -1)
				return;
			int interval = 0;
			// Get First Point
			if(pointsCount > 0){
				double yRel = yArray1[0];
				prevX = x2;
				prevY1 = y1 -(int)Math.round(yRel*YAxisDist);
				g.setColor(Color.RED);
				g.drawOval(prevX, prevY1, pointDiameter, pointDiameter);

				yRel = yArray2[0];
				prevY2 = y1 -(int)Math.round(yRel*YAxisDist);
				g.setColor(Color.BLUE);
				g.drawOval(prevX, prevY2, pointDiameter, pointDiameter);
				interval = XAxisDist/pointsCount;
			}
			else{
				g.drawOval(x2, y1, pointDiameter, pointDiameter);
				prevX = x2;
				prevY1 = y1;
				prevY2 = y2;
			}
			for(int i = 1; i <= pointsCount; i++)
			{
				double yRelative = yArray1[i];
				int xPoint = x2 + i*interval;
				int yPoint1 = y1 - (int)Math.round(yRelative*YAxisDist);
				g.setColor(Color.RED);
				g.drawOval(xPoint, yPoint1, pointDiameter, pointDiameter);
				g.drawLine(prevX, prevY1, xPoint, yPoint1);
				prevY1 = yPoint1;

				yRelative = yArray2[i];
				int yPoint2 = y1 - (int)Math.round(yRelative*YAxisDist);
				g.setColor(Color.BLUE);
				g.drawOval(xPoint, yPoint2, pointDiameter, pointDiameter);
				g.drawLine(prevX, prevY2, xPoint, yPoint2);
				prevX = xPoint;
				prevY2 = yPoint2;
			}
		}
	}


	JPanel makeBottomPanel()
	{
		JPanel panel = new JPanel();

		JButton resetB = new JButton("Reset");
		resetB.addActionListener(
				new ActionListener() {
					public void actionPerformed(ActionEvent a)
					{	
						reset();	
						repaint();
					}
				}
		);
		panel.add(resetB);

		panel.add(new JLabel("          "));
		JButton nextB = new JButton("Next");
		nextB.addActionListener(
				new ActionListener() {
					public void actionPerformed(ActionEvent a)
					{	
						try {
							nextStep();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}	
						repaint();
					}
				}
		);
		panel.add(nextB);


		panel.add(new JLabel("  "));
		JButton goB = new JButton("Go");
		goB.addActionListener(
				new ActionListener() {
					public void actionPerformed(ActionEvent a)
					{	go();	}
				}
		);
		panel.add(goB);

		panel.add(new JLabel("  "));
		JButton pauseB = new JButton("Pause");
		pauseB.addActionListener(
				new ActionListener() {
					public void actionPerformed(ActionEvent a)
					{
						pause();
					}
				}
		);
		panel.add(pauseB);
		
		panel.add(new JLabel("  "));
		JButton killMachineB = new JButton("Kill a machine");
		killMachineB.addActionListener( 
				new ActionListener() {
					public void actionPerformed(ActionEvent a)
					{
						try {
							killMachineRandom();
							repaint();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
		);
		panel.add(killMachineB);
		
		panel.add(new JLabel("  "));
		JButton restartMachinesB = new JButton("Restart Machines");
		restartMachinesB.addActionListener( 
				new ActionListener() {
					public void actionPerformed(ActionEvent a)
					{
						restartFailedMachines();
						repaint();
					}
				}
		);		
		panel.add(restartMachinesB);

		panel.add(new JLabel("           "));
		JButton quitB = new JButton("Quit");
		quitB.addActionListener(
				new ActionListener() {
					public void actionPerformed(ActionEvent a)
					{
						rescheduleLogPath.delete();
						System.exit(0);
					}
				}
		);
		panel.add(quitB);

		return panel;
	}

	void makeFrame()
	{
		JFrame frame = new JFrame();
		frame.setSize(1000, 600);
		frame.setTitle("Job Simulator");
		Container cPane = frame.getContentPane();
		cPane.add(makeBottomPanel(), BorderLayout.SOUTH);
		cPane.add(this, BorderLayout.CENTER);
		frame.setVisible(true);
	}

	/************************************************************
	 * 				Helper Classes - JOB and EVENT				*
	 ************************************************************/
	static public class Job {
		double entryTime, cpuUsage, ramUsage, endTime;
		String jobID;
		boolean isScheduled = false;
		boolean migrationAllowed = true;
		boolean isPaused;

		// Make sure to fill out params manually
		public Job()
		{
		}
		public Job (double entryTime,String jobId, double cpuReq, double ramReq)
		{
			this.entryTime = entryTime;
			this.jobID = jobId;
			this.cpuUsage = cpuReq;
			this.ramUsage = ramReq;
		}
	}

	static public class machineList {

		LinkedList<Job> jobList;
		//Constructor
		public machineList()
		{
			jobList = new LinkedList<Job>();
		}
		//Adds new job
		void addToJobList(double entryTime, String jobId, double cpuReq, double ramReq)
		{
			jobList.add(new Job(entryTime, jobId, cpuReq, ramReq));
		}
		//Getters
		public double getEntryTime(int pos)
		{
			return jobList.get(pos).entryTime;
		}
		public double getCpuUsage(int pos)
		{
			return jobList.get(pos).cpuUsage;
		}
		public double getRamUsage(int pos)
		{
			return jobList.get(pos).ramUsage;
		}
		public String getJobID(int pos)
		{
			return jobList.get(pos).jobID;
		}
		public boolean isScheduled(int pos)
		{
			return jobList.get(pos).isScheduled;
		}	
		public int getJobIndex(String findJobID)
		{
			for(int i = 0; i < jobList.size(); i++)
			{
				if(jobList.get(i).jobID.equals(findJobID))
					return i;
			}
			return -1;
		}
	}

	// This will hold our job events. For now it has SCHEDULE and DEPARTURE
	// It can be extended to account for 'priority' if we want to add that later

	static public class Event implements Comparable {
		//final static int SCHEDULE = 1;
		//final static int DEPARTURE = 2;
		final static int ARRIVAL = 1;
		final static int SCHEDULED = 2;
		final static int FAILED = 3;
		final static int KILLED = 4;
		final static int LOST = 5;

		int type = -1;                     // Arrival or departure.
		double eventTime;                  // When it occurs.
		int machine = -1;               // machine a jobs is scheduled on
		Job job;
		
		public Event (double eventTime, int type, int machine, Job job)
		{
			this.eventTime = eventTime;
			this.type = type;
			this.machine = machine;
			this.job = job;
		}

		public int compareTo (Object obj)
		{
			Event e = (Event) obj;
			if (eventTime < e.eventTime) {
				return -1;
			}
			else if (eventTime > e.eventTime) {
				return 1;
			}
			else {
				return 0;
			}
		}

		public boolean equals (Object obj)
		{
			return (compareTo(obj) == 0);
		}

	}
}




